/**
 * Licensed under the MIT License
 *
 * Copyright (c) 2012 Gary Paluk, http://www.plugin.io
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://www.opensource.org/licenses/mit-license.php
 */

package io.plugin.collections.core 
{
	
	import io.plugin.collections.interfaces.ICollection;
	import io.plugin.collections.interfaces.IIterator;
	import io.plugin.collections.interfaces.IList;
	import io.plugin.collections.interfaces.IListIterator;
	import io.plugin.core.errors.ConcurrentModificationError;
	import io.plugin.core.errors.IllegalStateError;
	import io.plugin.core.errors.NoSuchElementError;
	import io.plugin.core.errors.UnsupportedOperationError;
	import io.plugin.collections.ns.plugin_collections;
	
	/**
	 * ...
	 * @author Gary Paluk - http://www.plugin.io
	 */
	public class AbstractList extends AbstractCollection implements IList 
	{
		
		use namespace plugin_collections;
		
		private var mModCount: int;
		
		public function get modCount(): int
		{
			return mModCount;
		}
		
		public function set modCount( value: int ): void
		{
			mModCount = value;
		}
		
		public function AbstractList() 
		{
			
		}
		
		override public function add(o:Object):Boolean 
		{
			addAt( size(), o );
			return true;
		}
		
		public function getAt( index: uint ): Object
		{
			throw new UnsupportedOperationError();
		}
		
		public function setAt( index: uint, o: Object ): Object
		{
			throw new UnsupportedOperationError();
		}
		
		public function addAt( index: uint, o: Object ): void
		{
			throw new UnsupportedOperationError();
		}
		
		public function removeAt( index: uint ): Object
		{
			throw new UnsupportedOperationError();
		}
		
		public function indexOf( o : Object ): int
		{
			var iter: IListIterator = listIterator();
			if ( o == null ) {
				while ( iter.hasNext() )
					if ( iter.next() == null )
						return iter.previousIndex();
			} 
			else
			{
				while ( iter.hasNext() )
					if ( o.equals( iter.next() ) )
						return iter.previousIndex();
			}
			
			return -1;
		}
		
		public function lastIndexOf( o: Object ): int
		{
			var iter: IListIterator = listIteratorAt( size() );
			if ( o == null )
			{
				while ( iter.hasPrevious() )
					if ( iter.previous() == null )
						return iter.nextIndex();
			}
			else
			{
				while ( iter.hasPrevious() )
					if ( o.equals( iter.previous() ) )
						return iter.nextIndex();
			}
			return -1;
		}
		
		override public function clear(): void
		{
			removeRange( 0, size() );
		}
		
		public function addAllAt( index: uint, c:ICollection ): Boolean
		{
			rangeCheckForAdd( index );
			var modified: Boolean = false;
			var iter: IIterator = c.iterator();
			while ( iter.hasNext() )
			{
				addAt( index++, iter.next() );
				modified = true;
			}
			return modified;
		}
		
		
		override public function iterator():IIterator
		{
			return new Itr( this );
		}
		
		public function listIterator(): IListIterator
		{
			return listIteratorAt( 0 );
		}
		
		public function listIteratorAt( index: uint ): IListIterator
		{
			rangeCheckForAdd( index );
			
			return new ListItr( this, index );
		}
		
		// TODO implement RandomAccessSubList and SubList
		public function subList( fromIndex: uint, toIndex: uint ): IList
		{
			throw new UnsupportedOperationError();
			/*
			return ( this is RandomAccess ) ?
					new RandomAccessSubList( this, fromIndex, toIndex ) :
					new SubList( this, fromIndex, toIndex );
					*/
		}
		
		override public function equals(o:Object):Boolean 
		{
			if ( o == this )
				return true;
			
			if ( !(o is IList ) )
				return false;
			
			var iter1: IListIterator = listIterator();
			var iter2: IListIterator = IList( o ).listIterator();
			while ( iter1.hasNext() && iter2.hasNext() )
			{
				var obj1: Object = iter1.next();
				var obj2: Object = iter2.next();
				
				// TODO change this to a static equality check
				if ( !( obj1 == null ? obj2 == null : obj1.equals( obj2 ) ) )
				{
					return false;
				}
				
			}
			return !( iter1.hasNext() || iter2.hasNext() );
		}
		
		protected function removeRange( fromIndex: uint, toIndex: uint ): void
		{
			var iter: IListIterator = listIteratorAt( fromIndex );
			for ( var i: int = 0, n: int = toIndex - fromIndex; i < n; i++ )
			{
				iter.next();
				iter.remove();
			}
		}
		
		protected function rangeCheckForAdd( index: uint ): void
		{
			if ( index < 0 || index > size() )
			{
				throw new RangeError( outOfBoundsMessage( index ) );
			}
		}
		
		private function outOfBoundsMessage( index: int ): String
		{
			return "Index: " + index + ", Size : " + size(); 
		}
		
	}
	
}

import io.plugin.collections.interfaces.ICollection;
import io.plugin.collections.interfaces.IIterator;
import io.plugin.collections.interfaces.IListIterator;
import io.plugin.collections.ns.plugin_collections;
import io.plugin.collections.core.AbstractList;
import io.plugin.core.errors.IndexOutOfBoundsError;
import io.plugin.core.errors.NoSuchElementError;
import io.plugin.core.errors.ConcurrentModificationError;
import io.plugin.core.errors.IllegalStateError;
import io.plugin.core.errors.UnsupportedOperationError;
import io.plugin.collections.interfaces.IList;

internal class Itr implements IIterator
{
	
	use namespace plugin_collections;
	
	protected var mCursor: int = 0;
	protected var mLastRet: int = -1;
	protected var mExtectedModCount: int;
	
	protected var mList: AbstractList;
	
	public function Itr( list: AbstractList )
	{
		mList = list;
		
		mExtectedModCount = mList.modCount;
	}
	
	public function hasNext(): Boolean
	{
		return mCursor != mList.size();
	}
	
	public function next(): Object
	{
		checkForComodification();
		try
		{
			var i: int = mCursor;
			var next: Object = mList.getAt( i );
			mLastRet = i;
			mCursor = i + 1;
			
		}
		catch ( e: RangeError )
		{
			checkForComodification();
			throw new NoSuchElementError();
		}
		return next;
	}
	
	public function remove(): void
	{
		if ( mLastRet < 0 )
		{
			throw new IllegalStateError();
		}
		
		checkForComodification();
		
		try
		{
			mList.removeAt( mLastRet );
			if ( mLastRet < mCursor )
				mCursor--;
			mLastRet = -1;
			mExtectedModCount = mList.modCount;
		}
		catch ( e: RangeError )
		{
			throw new ConcurrentModificationError();
		}
		
	}
	
	protected final function checkForComodification(): void
	{
		if ( mList.modCount != mExtectedModCount )
		{
			throw new ConcurrentModificationError();
		}
	}
	
}

internal class ListItr extends Itr implements IListIterator
{
	
	use namespace plugin_collections;
	
	public function  ListItr( list: AbstractList, index: uint )
	{
		super( list );
		mCursor = index;
	}
	
	public function hasPrevious(): Boolean
	{
		return mCursor != 0;
	}
	
	public function previous(): Object
	{
		checkForComodification();
		try
		{
			var i: int = mCursor - 1;
			var previous: Object = mList.getAt( i );
			mLastRet = mCursor = i;
			
		}
		catch ( e: RangeError )
		{
			checkForComodification();
			throw new NoSuchElementError();
		}
		return previous;
	}
	
	public function nextIndex(): uint
	{
		return mCursor;
	}
	
	public function previousIndex(): uint
	{
		return mCursor - 1;
	}
	
	public function set( o: Object ): void
	{
		if ( mLastRet < 0 )
			throw new IllegalStateError();
		checkForComodification();
		
		try
		{
			mList.setAt( mLastRet, o );
			mExtectedModCount = mList.modCount;
		}
		catch ( e: RangeError )
		{
			throw new ConcurrentModificationError();
		}
	}
	
	public function add( o: Object ): void
	{
		checkForComodification();
		
		try
		{
			var i: int = mCursor;
			mList.addAt( i, o );
			mLastRet = -1;
			mCursor = i + 1;
			mExtectedModCount = mList.modCount;
		}
		catch ( e: RangeError )
		{
			throw new ConcurrentModificationError();
		}
	}
	
}

internal class SubList extends AbstractList
{
	
	use namespace plugin_collections;
	
	private var mList: AbstractList;
	private var mOffset: int;
	private var mSize: int;
	
	public function SubList( list: AbstractList, fromIndex: uint, toIndex: uint )
	{
		if ( fromIndex < 0 )
			throw new RangeError( "fromIndex = " + fromIndex );
			
		if ( toIndex > mList.size() )
			throw new RangeError( "toIndex = " + toIndex );
		
		if ( fromIndex > toIndex )
			throw new RangeError( "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")" );
		
		mList = list;
		mOffset = fromIndex;
		mSize = toIndex - fromIndex;
		modCount = mList.modCount;
	}
	
	override public function setAt(index:uint, o:Object):Object 
	{
		rangeCheck( index );
		checkForComodification();
		return mList.setAt( index + mOffset, o );
	}
	
	override public function getAt(index:uint):Object 
	{
		rangeCheck( index );
		checkForComodification();
		return mList.getAt( index + mOffset );
	
	}
	
	override public function size():uint 
	{
		checkForComodification();
		return mSize;
	}
	
	override public function addAt(index:uint, o:Object):void 
	{
		rangeCheckForAdd( index );
		checkForComodification();
		mList.addAt( index + mOffset, o );
		modCount = mList.modCount;
		mSize++;
	}
	
	override public function removeAt(index:uint):Object 
	{
		rangeCheck( index );
		checkForComodification();
		var result: Object = mList.removeAt( index + mOffset );
		modCount = mList.modCount;
		mSize--;
		return result;
	}
	
	override protected function removeRange( fromIndex: uint, toIndex: uint ): void
	{
		checkForComodification();
		removeRange( fromIndex + mOffset, toIndex + mOffset )
		modCount = mList.modCount;
		mSize -= (toIndex - fromIndex);
	}
	
	override public function addAll(c:ICollection):Boolean 
	{
		return addAllAt( mSize, c );
	}
	
	override public function addAllAt(index:uint, c:ICollection):Boolean 
	{
		rangeCheckForAdd( index );
		var cSize: int = c.size();
		if ( cSize == 0 )
			return false;
		
		checkForComodification();
		mList.addAllAt( mOffset + index, c );
		modCount = mList.modCount;
		mSize += cSize;
		return true;
	}
	
	override public function iterator():IIterator 
	{
		return listIterator();
	}
	
	// TODO implement IListIterator code
	override public function listIteratorAt(index:uint):IListIterator 
	{
		throw new UnsupportedOperationError();
	}
	
	override public function subList( fromIndex: uint, toIndex: uint ): IList
	{
		return new SubList( this, fromIndex, toIndex );
	}
	
	private function rangeCheck( index: uint ): void
	{
		if ( index < 0 || index >= mSize )
			throw new RangeError( outOfBoundsMsg( index ) );
	}
	
	override protected function rangeCheckForAdd( index: uint ): void
	{
		if ( index < 0 || index > mSize )
			throw new RangeError( outOfBoundsMsg( index ) );
	}
	
	private function outOfBoundsMsg( index: uint ): String
	{
		return "Index: " + index + ", Size: " + mSize;
	}
	
	private function checkForComodification(): void
	{
		if ( modCount != mList.modCount )
			throw new ConcurrentModificationError();
	}
	
}